#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/types.h>
#include <linux/ide.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

/**********************   defines   **********************/

/**********************   defines   **********************/

/**********************   struct defines   **********************/


/**********************   Function prototype   **********************/
static int gpioled_open(struct inode *inode, struct file *file);
static int gpioled_release(struct inode *inode, struct file *file);
static ssize_t gpioled_read(struct file *file, char *buf, size_t size, loff_t *offset);
static ssize_t gpioled_write(struct file *file, const char *buf, size_t size, loff_t *offset);

void user_gpioled_switch(u8 status);


/**********************   user defines   **********************/

/* dev core struct */
#define GPIOLED_CNT		    1
#define GPIOLED_NAME     "gpioled"

#define LED_OFF 	0		/* led off */
#define LED_ON  	1		/* led on */

struct core_gpioled_dev{
    dev_t dev_id;           /* dev id   */
    int major;              /* major id */
    int minor;              /* minor id */
    struct cdev cdev;       /* cdev     */
    struct class *class;
    struct device *device;
    struct device_node * node;
    int led_gpio;
    atomic_t lock;    /* atomic var */
};

struct core_gpioled_dev gpioled;

struct file_operations gpioled_fops = {
    .open = gpioled_open,
    .release = gpioled_release,
    .read = gpioled_read,
    .write = gpioled_write,
};

static int  __init gpioled_init(void);
static void __exit gpioled_exit(void);

/* module init and exit */
module_init(gpioled_init);
module_exit(gpioled_exit);

/* license  and author */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("huafv");

/**********************   Function implementation   **********************/
static int  __init gpioled_init(void) {
    int ret = 0;
    atomic_set(&gpioled.lock, 1);
    printk(KERN_INFO "gpioled_init\n");

    /* register device */
    gpioled.major = 0;
    gpioled.minor = 0;

    if(gpioled.major) {
        gpioled.dev_id = MKDEV(gpioled.major, 0);
        ret = register_chrdev_region(gpioled.dev_id,  GPIOLED_CNT, GPIOLED_NAME);
    }else {
        ret = alloc_chrdev_region(&gpioled.dev_id, 0, GPIOLED_CNT, GPIOLED_NAME);
        gpioled.major = MAJOR(gpioled.dev_id);
        gpioled.minor = MINOR(gpioled.dev_id);
    }
    printk(KERN_INFO "gpioled_init register returns %d\n", ret);
    printk(KERN_INFO "gpioled_init register major %d\n", gpioled.major);
    printk(KERN_INFO "gpioled_init register minor %d\n", gpioled.minor);

    /* add chr dev */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);
    cdev_add(&gpioled.cdev, gpioled.dev_id, GPIOLED_CNT);

    /* create class */
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if(IS_ERR(gpioled.class)) {
        printk(KERN_WARNING "gpioled.create failed\n");
    }

    /* create device */
    gpioled.device = device_create(gpioled.class, NULL, gpioled.dev_id, NULL, GPIOLED_NAME);
    if(IS_ERR(gpioled.device)) {
        printk(KERN_WARNING "gpioled.create failed\n");
    }

    printk(KERN_INFO "gpioled.create register returns %d\n", ret);

    /* find device node */
    gpioled.node = of_find_node_by_path("/huafvgpio");

    /* get gpio */
    gpioled.led_gpio = of_get_named_gpio(gpioled.node, "led-gpios", 0);

    /* request gpio */
    ret = gpio_request(gpioled.led_gpio, "led-gpio");
    if(ret < 0) {
        printk(KERN_WARNING "gpioled.create failed free gpio next requset\n");
        gpio_free(gpioled.led_gpio);
        ret = gpio_request(gpioled.led_gpio, "led-gpio");
    }
    printk(KERN_INFO "gpioled.create gpio_request returns %d\n", ret);


    /* set gpio dir */
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    printk(KERN_INFO "gpioled.create gpio_direction_output returns %d\n", ret);

    /* set gpio value */
    gpio_set_value(gpioled.led_gpio, 0);

    return 0;
}

static void __exit gpioled_exit(void) {
    printk(KERN_INFO "gpioled_exit\n");
    user_gpioled_switch(LED_OFF);
    gpio_free(gpioled.led_gpio);
    device_destroy(gpioled.class, gpioled.dev_id);
    class_destroy(gpioled.class);
    cdev_del(&gpioled.cdev);
    unregister_chrdev_region(gpioled.dev_id, GPIOLED_CNT);
}

static int gpioled_open(struct inode *inode, struct file *file) {
    printk(KERN_INFO "gpioled_open\n");

    if (!atomic_dec_and_test(&gpioled.lock)) {
        atomic_inc(&gpioled.lock);
        return -EBUSY;
    }


    file->private_data = &gpioled;
    return 0;
}
static int gpioled_release(struct inode *inode, struct file *file) {
    struct core_gpioled_dev * dev  =  file->private_data;
    atomic_inc(&dev->lock);
    printk(KERN_INFO "gpioled_release\n");

    return 0;
}
static ssize_t gpioled_read(struct file *file, char *buf, size_t size, loff_t *offset) {
    printk(KERN_INFO "gpioled_read\n");

    return 0;
}
static ssize_t gpioled_write(struct file *file, const char *buf, size_t size, loff_t *offset) {
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    printk(KERN_INFO "gpioled_write\n");

    retvalue = copy_from_user(databuf, buf, size);
    if(retvalue < 0) {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0];		/* 获取状态值 */

    if(ledstat == LED_ON) {
        user_gpioled_switch(LED_ON);		/* 打开LED灯 */
    } else if(ledstat == LED_OFF) {
        user_gpioled_switch(LED_OFF);	    /* 关闭LED灯 */
    }
    return 0;
}

/**
 * @brief dtsled status switch
 * @param status led status in defines
 */
void user_gpioled_switch(u8 status)
{
    printk(KERN_INFO "gpioled_switch %d\n", status);
    if(status == LED_ON) {
        gpio_set_value(gpioled.led_gpio, 0);
    }else if(status == LED_OFF) {
        gpio_set_value(gpioled.led_gpio, 1);
    }
}

// /home/huafv/linux/nfs/rootfs/lib/modules/4.1.15-g3dc0a4b/